package com.region.loadbalancer.monitor;

import com.region.common.adapter.logging.LoggerAdapter;
import com.region.common.adapter.logging.LoggerAdapterFactory;
import com.region.loadbalancer.monitor.event.Event;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Server Monitor
 *
 * @author liujieyu
 * @date 2023/5/25 20:00
 * @desciption
 */
public class ServerMonitor {

    private final LoggerAdapter loggerAdapter = LoggerAdapterFactory.getLogger(ServerMonitor.class);

    private static ServerMonitor monitor = new ServerMonitor();

    public static ServerMonitor getInstance() {
        return monitor;
    }

    private BlockingQueue<Event> workQueue = new LinkedBlockingQueue<>();

    private int coreNumber = Runtime.getRuntime().availableProcessors();

    private int timeInterval = 10;

    private TimeUnit detectionTimeType = TimeUnit.SECONDS;

    private ScheduledThreadPoolExecutor scheduledExecutor = null;

    // Whether you are listening
    private AtomicBoolean isMonitor = new AtomicBoolean(false);
    // Whether you are shutdown
    private AtomicBoolean isShutdown = new AtomicBoolean(false);
    /**
     * Set parameter config of sever monitor
     * @param core
     * @param interval
     * @param unit
     */
    public void setMonitorConfig(int core, int interval, TimeUnit unit) {
        if (core > 0) {
            this.coreNumber = core;
        }
        if (interval > 0) {
            this.timeInterval = interval;
        }
        if (unit != null) {
            this.detectionTimeType = unit;
        }
    }

    /**
     * Submit an event
     *
     * @param event
     */
    public void submitAsyncEvent(Event event) {
        if (scheduledExecutor != null && scheduledExecutor.isShutdown()) {
            loggerAdapter.info("The server monitor is shutdown.");
            return;
        }
        workQueue.offer(event);
    }

    /**
     * Synchronous execution of events
     *
     * @param event
     */
    public void submitSyncEvent(Event event) {
        if (scheduledExecutor != null && scheduledExecutor.isShutdown()) {
            loggerAdapter.info("The server monitor is shutdown.");
            return;
        }
        Future<Integer> syncEvent = scheduledExecutor.submit(() -> {
            event.run();
            return 0;
        });
        try {
            syncEvent.get();
        } catch (Exception e) {
            loggerAdapter.error("Submit Sync Event failure", e);
        }
    }

    /**
     * The first loading schedule executor
     */
    public void loadSchedulerExecutor() {
        if (scheduledExecutor == null) {
            scheduledExecutor =
                    new ScheduledThreadPoolExecutor(this.coreNumber,
                            new ThreadFactory() {
                                AtomicInteger threadNumber = new AtomicInteger(1);
                                ThreadGroup group = (System.getSecurityManager() != null) ? System.getSecurityManager().getThreadGroup() :
                                        Thread.currentThread().getThreadGroup();

                                @Override
                                public Thread newThread(Runnable r) {
                                    Thread t = new Thread(group, r,
                                            "LoadBalancerServerMonitor-" + threadNumber.getAndIncrement(),
                                            0);
                                    if (t.isDaemon())
                                        t.setDaemon(false);
                                    if (t.getPriority() != Thread.NORM_PRIORITY)
                                        t.setPriority(Thread.NORM_PRIORITY);
                                    return t;
                                }
                            }, new ThreadPoolExecutor.DiscardPolicy());
        }
        loggerAdapter.info("The scheduler executor information is " + scheduledExecutor);
        monitor();
        Runtime.getRuntime().addShutdownHook(new Thread(() -> shutdown()));
    }

    /**
     * Shutdown the server monitor
     */
    public synchronized void shutdown() {
        if (!isShutdown.get()) {
            loggerAdapter.info("The server monitor shutdown now.");
            scheduledExecutor.shutdownNow();
            isShutdown.set(true);
            scheduledExecutor = null;
        }
        loggerAdapter.info("The server monitor was been shut down.");
    }
    /**
     * Whether you are shutdown
     */
    public boolean isShutdown() {
        return isShutdown.get();
    }

    private void monitor() {
        scheduledExecutor.execute(() -> {
            try {
                if (isMonitor.getAndSet(true)) {
                    loggerAdapter.warn("The monitor is working...");
                    return;
                }
                while (true) {
                    loggerAdapter.info("Start listening for events...");
                    Event take = workQueue.take();
                    if (loggerAdapter.isDebugEnabled()) {
                        loggerAdapter.debug("Get the current execution event class type is " + take.getClass());
                    }
                    // Running Event
                    switch (take.state()) {
                        case NOW:
                            scheduledExecutor.execute(take);
                            break;
                        case SCHEDULE:
                            scheduledExecutor.schedule(take, this.timeInterval, this.detectionTimeType);
                            break;
                        case SCHEDULE_CYCLE:
                            // First immediate implementation for current events
                            scheduledExecutor.scheduleWithFixedDelay(take, 0, this.timeInterval, this.detectionTimeType);
                            break;
                        default:
                            // do nothing
                    }
                }
            } catch (InterruptedException e) {
                if (e instanceof InterruptedException) {
                    loggerAdapter.info("Service monitor thread interrupted error. the exception message is " + e.getMessage());
                } else {
                    loggerAdapter.error("Service monitor timed time to handle exceptions", e);
                    isMonitor.set(false);
                    // continue server monitor
                    monitor();
                }
            }
        });
    }

}
